Eddie's Lessons
Beginners Programming
After all that work, let me tell you - there is a better way. Liberty Basic introduced Sprites with the release of version 2, many years ago. They are a simple way to integrate animated graphics, such as in games, into Liberty Basic. We will only be introducing sprites in this installment, scratching the surface as it were. There is a lot to them, and a lot that can be done with them.
I am going to recommend you spend some time reviewing the Sprites section in the helpfile for a broader understanding of sprites. Also, Alyce Watson has been presenting sprites in her great newsletter series SpriteByte. It is a must read for anyone coming into sprites for the first time (as well as any veteran LB programmer - I learn something new all the time there!). [Editors note: I see that Alyce has gone back to the beginning this month in her SpriteByte article for May - it is an excellent oppurtunity to ground yourself in how and why sprites work.]
Sprites are specially defined graphics that you work with using special sprite commands in Liberty Basic. They can be any shape or size. Liberty Basic leverages a couple special Windows core functions (API functions) to make sprites seem to float over other graphic elements in a graphicbox. Sprites are only supported in graphicboxes or a graphic window. You also are limited to using sprites in a single graphicbox at a time.
The magic of the Sprite is possible because of the special bulk bit copy function which supports copying graphic elements in huge chunks quickly. To define the parts of a given graphic to copy (and by exclusion the parts to not copy) you must create your sprite graphics with a MASK that defines the parts you want to show the user. Masks are part of the sprite graphic which are attached above the main image your users will see. The mask is like a negative, having pure black pixels in the area of the graphic that you want your users to see. Parts of the main image to exclude should also be filled with black pixels. This is like the main image background. It is best explained with a picture:

When the mask is applied over the main image and manipulated with the special functions that Windows exposes Liberty Basic is able to make the main image (the yellow face in example here) float on to of other graphics, including a graphics background.
Creating sprites with a mask by hand can be a bit tedious. Many years ago Alyce Watson developed a nice little sprite masking program that can take an ordinary bitmap and create and attach a mask for it. This new graphic (a sprite graphic) can then be saved. In order to use this program your image should not contain pure black except in the areas you do not want to display (the background). By this account, the background must be black. The program is included in full in the LB helpfile. It is also in the zip file (begprog11.zip) for this article as part of the attachments available to those of you who download the newsletter. It is called masker.bas.
Leveraging what we have done so far with our pong game, let now adapt it for use with sprites. We will begin to learn how to work with sprites and create a whimsical game at the same time. In this case, I am going to use a cherry as my "ball". I have included in the zip file (begprog11.zip) for this article a directory containing the unmasked graphics for the game we are creating. (The masked graphics and source code for the sprite based game, cherrypong.bas, are also included there.) If you want to try your hand at masking, load the masker.bas program and mask each of the "cherry" graphics. I have already masked the paddle.
You will soon see how easy it is to work with sprite, but coming up to speed can be a bit daunting. We will be using the following commands. I recommend you check them out in the helpfile. We will not be covering them is detail here this time around (although we may discuss them more in later installments):
print #w.g, "spritexy SpriteName 100 137";
OR
x=100:y=137
print #w.g, "spritexy SpriteName ";x;" ";y
This causes the sprite called SpriteName to be drawn at position x, y the next time the display is updated with the DRAWSPRITES command.
print #w.g, "spriteround SpriteName";
This causes the sprite called SpriteName to be assumed to be a rounded area within the rectangular bitmap when collisions are evaluated.
print #w.g, "addsprite SpriteName BmpName";
This adds a sprite with name SpriteName from loaded bitmap called BmpName.
print #w.g, "addsprite SpriteName bmp1 bmp2 bmp3 ... bmpLast";
This adds a sprite with name SpriteName from loaded bitmaps - may include any number of bitmaps.
print #w.g, "background BmpName";
This sets the background for sprites to be the loaded bitmap called BmpName.
print #w.g, "cyclesprite SpriteName 1"
print #w.g, "cyclesprite SpriteName -1"
print #w.g, "cyclesprite SpriteName 1 once"
This causes a sprite to cycle through its image list automatically. Using "1" will cause the list to cycle forward. Using "-1" will cause the list to cycle backwards. Using the optional "once" parameter will cause the sprite to cycle through its image list only one time, other wise it cycles continuously.
print #w.g, "drawsprites";
This causes all visible sprites to be drawn on the background and it updates the display.
print #w.g, "spritecollides SpriteName";
input #w.g, list$
OR
print #w.g, "spritecollides SpriteName list$";
This causes a list of all sprites that collided with the sprite named SpriteName to be contained in the variable called "list$".
In addition to these, we also must use the LOADBMP command:
LOADBMP "name", "filename.bmp"
Description:
The first version of this command loads a standard Windows *.BMP bitmap file on disk into Liberty BASIC. The "name" is a string chosen to describe the bitmap being loaded and the "filename.bmp" is the actual name of the bitmap disk file. Once loaded, the bitmap can then be displayed in a graphics window type using the DRAWBMP command
Note that sprites can be loaded as other graphic types using Liberty Basic's DLL functions and special DLL that work with Liberty Basic. Then they can be converted to bitmaps in memory and loaded using an alternate form of the LOADBMP command. We will not be handling the more advanced topics at this time. If you are interested in this, check out the many graphics based articles in the LB Newsletter.
If you masked the bitmaps earlier, you will know that we have eight cherries, each rotated 45 degree from the previous. We will load each of these using the LOADBMP command, and then later assemble them into a single sprite using the ADDSPRITE command. I also have a nice background image (called bkgnd.bmp) included in the zip file. We will load it as well when we load the sprite images. I like to load my graphics during program initialization. Here is the code to add to the beginning of the program:
'load the bitmaps
loadbmp "cherry0", "cherry0.bmp"
loadbmp "cherry1", "cherry1.bmp"
loadbmp "cherry2", "cherry2.bmp"
loadbmp "cherry3", "cherry3.bmp"
loadbmp "cherry4", "cherry4.bmp"
loadbmp "cherry5", "cherry5.bmp"
loadbmp "cherry6", "cherry6.bmp"
loadbmp "cherry7", "cherry7.bmp"
loadbmp "pdl", "paddle.bmp"
loadbmp "bkgnd", "bkgnd.bmp"
Remember you must have these graphic files in the same directory that you are working in with Liberty Basic to actually load the images and prevent an error from occurring.
Once the bitmaps are loaded we can assemble the sprites. We will be using two sprites. One called "paddle" and another called "ball". We assemble these and add them using the ADDSPRITE command. Sprite commands are like graphic commands. They are dependant on an existing graphicbox. You must wait until the window has been opened to add the sprites. Remove the following code from the pong program we created earlier:
'create the paddle at the starting location
#main.gfx "color white; backcolor red"
#main.gfx "up; goto 465 ";paddleloc;"; down; boxfilled 475 ";paddleloc+paddlesize
Replace it with the following code to add the sprites:
'create the sprites
#main.gfx "addsprite ball c0 c1 c2 c3 c4 c5 c6 c7"
#main.gfx "addsprite paddle pdl"
Next lets add a nice background to the game - the BACKGROUND command will allow us to do that by applying a graphic which was previously loaded. Add this code directly following the code we just added:
'add a background
#main.gfx "background bg"
We also need to place the paddle sprite on the game board. We can reuse some of the old paddle drawing code for calculating the location for the paddle. We place sprites with the SPRITEXY command. Sprites are placed into the graphicbox at location 1,1 if they have not been explicitly located somewhere else. For now lets move the cherry sprite to a non-visible location. We will place the cherry sprite into a visible location later after the user presses the "GO" button. Add this code to place the sprites:
'place the sprites
#main.gfx "spritexy paddle 465 ";paddleloc
#main.gfx "spritexy ball 800 800"
Neither the sprites nor the background are visible until the DRAWSPRITES command has been executed. Add this code to make the background and the paddle sprite visible:
'make the background visible
#main.gfx "drawsprites"
One of the purposes of having eight sprite images for the cherry is to cycle them, making it appear as though it is spinning as it moves. We can set this cycling now in the initialization using the CYCLESPRITE command. Here is the code to add just after the last few lines of code:
'cycle the ball sprite
#main.gfx "cyclesprite ball 1"
The game can be run at this point to see the effect, although we have not yet integrated sprite movement. Try it out and see what it looks like.
We have to do some major work to the "[move]" subroutine to accommodate the new sprites. The good news is that much of this work consists of simplification of our earlier code. Before we do that, remove the following line from the "[go]" subroutine:
#main.gfx "color white"
The "[fetch]" subroutine goes unchanged. We no longer must draw our own paddle and ball in the "[move]" subroutine. This lets us track less stuff, like where the ball or paddle were last movement, and relieves us of the need to remove the old ball and paddle images before drawing the new ones. Here is the new sprite base "[move]" subroutine:
'process the actual paddle movement and prevent it from exceeding limits.
paddleloc = paddleloc + paddle
if paddleloc < -10 then paddleloc = -10
if paddleloc > 190 then paddleloc = 190
#main.gfx "spritexy paddle 465 ";paddleloc
#main.st1 "Speed: " + str$(abs(xchg))
#main.st3 "Y-change: " + str$(abs(ychg))
'process the ball movement
xloc = xloc + xchg
yloc = yloc + ychg
'there is a weird bug that occasionally occurs where y can keep getting smaller
'or it can keep getting bigger - fix it here
if yloc < -10 then yloc = -10
if yloc > 200 then yloc = 200
'place the ball sprite where we want it to be
#main.gfx "spritexy ball ";xloc;" ";int(yloc)
'draw the sprites now
#main.gfx "drawsprites"
#main.gfx "spritecollides paddle list$"
if instr(list$, "ball") > 0 then
'we hit the paddle - time to rebound...
goto [changedirection]
end if
'check to see if we should have hit the paddle
if xloc > 450 then
goto [quiting]
end if
if yloc < -8 or yloc > 196 then
ychg = ychg * -1
end if
if xloc < -2 or xloc > 482 then [changedirection]
wait
Notice it only took one sprite command each to place both the ball and the paddle. Also, notice that we issue a drawsprites command as soon as all the sprite elements where we want them. This is what gave the sprites motion.
We also changed the way we detect whether we have hit the paddle or not. Originally we checked to see if the ball was in the same vertical location as the paddle. Now we rely on the sprite command SPRITECOLLIDES to tell us whether a collision has occurred. Spritecollides is a bit more complex of the sprite commands. It returns a list of sprites that have collided with the named sprite. If there were no collisions an empty string is returned.
We have very few sprites, so collision detection is not too hard. We know the name of the sprite moving toward the paddle is "ball". All we need to do is see this sprite is listed in the collision list each time we update the display. If it is we will know that the ball hit the paddle and a rebound is required (we call the subroutine "[changedirection]").
Determining if we missed the ball with the paddle is a simple matter of checking to see whether the ball has traveled past the paddle. When this happens I call a new subroutine called "[quitting]". I have moved the code that is executed when the round is over to this new routine. This was not a requirement of the altered "[move]" routine, but it did simplify and improve the looks of it. Here is that new "[quitting]" routine:
[quitting]
timer 0
confirm "You missed the ball - game over!" + chr$(13) + _
"Would you like to play again?";answer$
if answer$ = "no" then
goto [quit]
else
ballspeed = 2
xchg = -10
ychg = ((int(rnd(0)*10)-3) / (int(rnd(0)*3)+1))
xloc = 430
yloc = 80
paddleloc = 85
paddle = 0
b$ = "##"
#main.go "!setfocus"
goto [start_over]
end if
There are some new things here. The first is the "Timer 0" command. This causes the timer to stop. This is important to stop the animation of the sprites. You do not notice this animation because there is a confirm window open, but it is important. If you did not stop the animation while this dialog window was open it would cause the sprites to shoot around the graphics window when focus was returned to it.
The next new thing (which I did for convenience) was set the focus to the "GO" button. This allows us to simply press enter to answer when we are queried to play again.
This pretty much covers the modifications required to create a sprite based graphics pong game. The complete code listing for the game is in Appendix B as well as in a bas file called CherryPong.bas in the attached archive. Next time we will be looking a little more at sprites, digging a bit more deeply into their operation and beginning a new project. This project is complete as far as this series goes, but you can go much further. Here are some ideas of what to add to make this a more interesting game:
Consider these the next challenge. We will not be reviewing how to implement these in future installments, but remember you learn by doing. You have a solid platform to experiment from. So experiment!
Thanks!